﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Ccr.Core;
using XcoAppSpaces.Core;
using XcoAppSpaces.Dialogs.Extensions;

namespace XcoAppSpaces.Dialogs
{
    public class DialogHost : Port<object>
    {
        private readonly Dictionary<Type, Func<Type, Dialog>> dialogMappings = new Dictionary<Type, Func<Type, Dialog>>();
        private readonly ReaderWriterLock rwlDialogMappings = new ReaderWriterLock();
        private readonly DispatcherQueue taskQueue;


        //[InjectionConstructor]
        //public DialogHost(XcoAppSpace space)
        //{
        //    this.taskQueue = space.DefaultDispatcherQueue;
        //}

        public DialogHost() : this(new DispatcherQueue()) {}
        public DialogHost(DispatcherQueue dpq)
        {
            if (dpq == null) dpq = new DispatcherQueue();
            this.taskQueue = dpq;

            Arbiter.Activate(
                this.taskQueue,
                Arbiter.Receive(
                    true,
                    this,
                    this.ProcessStartMessage
                    )
                );
        }


        public TLocalDialogType StartDialog<TLocalDialogType>(object message, IPort remoteDialogHost)
            where TLocalDialogType : Dialog
        {
            return (TLocalDialogType)StartDialog(message, typeof (TLocalDialogType), remoteDialogHost);
        }

        public Dialog StartDialog(object message, Type localDialogType, IPort remoteDialogHost)
        {
            Dialog dlg = Create_dialog(message.GetType(), t => (Dialog)Activator.CreateInstance(localDialogType));
            return Send_start_message_to_both_dialog_partners(dlg, message, remoteDialogHost);
        }

        public Dialog StartDialog(object message, Dialog dialogInstance, IPort remoteDialogHost)
        {
            dialogInstance.Start(this.taskQueue);
            return Send_start_message_to_both_dialog_partners(dialogInstance, message, remoteDialogHost);
        }

        public Dialog StartDialog(object message, IPort remoteDialogHost)
        {
            Dialog dlg = Create_dialog_for_start_message(message);
            return Send_start_message_to_both_dialog_partners(dlg, message, remoteDialogHost);
        }


            private Dialog Create_dialog(Type messageType, Func<Type, Dialog> createDialog)
            {
                var dlg = createDialog(messageType);
                dlg.Start(this.taskQueue);
                return dlg;
            }


            private Dialog Send_start_message_to_both_dialog_partners(Dialog localDialogInstance, object message, IPort remoteDialogHost)
            {
                Send_start_message_to_local_partner(localDialogInstance, message);
                Send_start_message_to_remote_partner(localDialogInstance, remoteDialogHost, message);
                return localDialogInstance;
            }


                private void Send_start_message_to_local_partner(Port<DialogMessage> localDialogPort, object message)
                {
                    localDialogPort.Post(new DialogMessage(message, null));
                }

                private void Send_start_message_to_remote_partner(IPort localDialogPort, IPort remoteDialogHost, object message)
                {
                    ICausality c = new Causality("DialogHost.StartDialog", null, localDialogPort);
                    Dispatcher.AddCausality(c);
                    remoteDialogHost.PostUnknownType(message);
                    Dispatcher.RemoveCausality(c);
                }


        public void Register<TStartMessage, TDialog>()
            where TDialog : Dialog, new()
        {
            Register<TStartMessage>(t => new TDialog());
        }

        public void Register<TStartMessage>(Func<Type, Dialog> createDialog)
        {
            rwlDialogMappings.ExecuteExclusively(
                () => this.dialogMappings.Add(typeof (TStartMessage), createDialog), 
                1000);
        }


        [XcoConcurrent]
        void ProcessStartMessage(object message)
        {
            Dialog dlg = Create_dialog_for_start_message(message);
            ICausality c = Retrieve_dialog_partner_port_from_causality();

            var dlgMsg = new DialogMessage(message, c.CoordinationPort);
            dlg.Post(dlgMsg);
        }


            private Dialog Create_dialog_for_start_message(object message)
            {
                Func<Type, Dialog> dialogFactory = null;
                rwlDialogMappings.ExecuteConcurrently(
                    () => { dialogFactory = this.dialogMappings[message.GetType()]; },
                    1000);
                return Create_dialog(message.GetType(), dialogFactory);
            }

            private ICausality Retrieve_dialog_partner_port_from_causality()
            {
                ICausality c = Dispatcher.ActiveCausalities.First();
                Dispatcher.RemoveCausality(c);
                return c;
            }
    }
}
